package org.finesys.common.security.authentication.handler;

import java.io.IOException;
import java.time.temporal.ChronoUnit;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.finesys.common.core.util.AsyncUtil;
import org.finesys.common.security.authentication.converter.CustomOAuth2AccessTokenResponseHttpMessageConverter;
import org.finesys.system.api.entity.SysLog;
import org.finesys.system.api.feign.LogServiceApi;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.SneakyThrows;

/**
 * 认证成功处理
 */
public class AuthAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new CustomOAuth2AccessTokenResponseHttpMessageConverter();

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        AsyncUtil.parallel(true, new AuthenticationSuccessLog(authentication, request), new AuthenticationSuccessResponse(authentication, response));
    }

    /**
     * 记录日志
     */
    public class AuthenticationSuccessLog implements Runnable {

        private final Authentication authentication;

        private final HttpServletRequest request;

        public AuthenticationSuccessLog(Authentication authentication, HttpServletRequest request) {
            this.authentication = authentication;
            this.request = request;
        }

        @Override
        public void run() {
            //请求信息不能为空
            if (request == null) {
                return;
            }
            String userName = request.getParameter(OAuth2ParameterNames.USERNAME);
            if(!StringUtils.hasText(userName)){
                userName=authentication.getName();
            }
            SysLog logDto = new SysLog();
            logDto.setTitle(String.format("用户[%s]登陆成功", userName));
            logDto.setUserAgent(userName);
            logDto.setCreateBy(userName);
            logDto.setRemoteAddr(ServletUtil.getClientIP(request));
            logDto.setRequestUri(request.getRequestURI());
            logDto.setParamsData(userName);
            logDto.setLogType(4);
            logDto.setMethod(request.getMethod());
            LogServiceApi logServiceApi = SpringUtil.getBean(LogServiceApi.class);
            logServiceApi.add(logDto);
        }
    }

    /**
     * 响应信息
     */


    public class AuthenticationSuccessResponse implements Runnable {

        private final Authentication authentication;

        private final HttpServletResponse response;

        public AuthenticationSuccessResponse(Authentication authentication, HttpServletResponse response) {
            this.authentication = authentication;
            this.response = response;
        }

        @SneakyThrows
        @Override
        public void run() {
            OAuth2AccessTokenAuthenticationToken accessTokenAuthenticationToken = (OAuth2AccessTokenAuthenticationToken) authentication;
            OAuth2AccessToken auth2AccessToken = accessTokenAuthenticationToken.getAccessToken();
            OAuth2RefreshToken auth2RefreshToken = accessTokenAuthenticationToken.getRefreshToken();
            Map<String, Object> additionalParameters = accessTokenAuthenticationToken.getAdditionalParameters();
            OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(auth2AccessToken.getTokenValue()).tokenType(auth2AccessToken.getTokenType()).scopes(auth2AccessToken.getScopes());
            if (auth2AccessToken.getIssuedAt() != null && auth2AccessToken.getExpiresAt() != null) {
                builder.expiresIn(ChronoUnit.SECONDS.between(auth2AccessToken.getIssuedAt(), auth2AccessToken.getExpiresAt()));
            }
            if (auth2RefreshToken != null) {
                builder.refreshToken(auth2RefreshToken.getTokenValue());
            }
            if (!ObjectUtils.isEmpty(additionalParameters)) {
                builder.additionalParameters(additionalParameters);
            }

            OAuth2AccessTokenResponse auth2AccessTokenResponse = builder.build();
            ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
            // 无状态 注意删除 context 上下文的信息
            SecurityContextHolder.clearContext();

            accessTokenHttpResponseConverter.write(auth2AccessTokenResponse, null, httpResponse);
        }
    }
}
